package service;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import model.command.Command;
import model.command.CommandPos;
import model.command.RmCommand;
import model.command.SetCommand;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import utils.CommandUtil;
import utils.CompressionUtils;
import utils.LoggerUtil;
import utils.RandomAccessFileUtil;

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static utils.CompressionUtils.decompressGZFile;

public class NormalStore implements service.Store {

    public static final String TABLE = ".table";
    public static final String RW_MODE = "rw";
    public static final String NAME = "data";
    private final Logger LOGGER = LoggerFactory.getLogger(NormalStore.class);
    private final String logFormat = "[NormalStore][{}]: {}";

    private TreeMap<String, Command> memTable;
    private HashMap<String, CommandPos> index;
    private final String dataDir;
    private final ReadWriteLock indexLock;
    private RandomAccessFile writerReader;
    private final int storeThreshold = 3;
    // TODO: 2024/7/2 测试才这么小，后面改过来 
    private static final long FILE_SIZE_THRESHOLD = 100; // 10kB
    private int fileCounter;

    public NormalStore(String dataDir) {
        this.dataDir = dataDir;
        this.indexLock = new ReentrantReadWriteLock();
        this.memTable = new TreeMap<>();
        this.index = new HashMap<>();

        File file = new File(dataDir);
        if (!file.exists()) {
            LoggerUtil.info(LOGGER, logFormat, "NormalStore", "dataDir isn't exist, creating...");
            file.mkdirs();
        }
        this.fileCounter = initializeFileCounter();
        this.reloadIndex();
    }

    private int initializeFileCounter() {
        File dir = new File(dataDir);
        File[] files = dir.listFiles((d, name) -> name.startsWith(NAME) && (name.endsWith(TABLE) || name.endsWith(TABLE + ".gz")));

        if (files == null || files.length == 0) {
            return 0;
        }

        int maxCounter = 0;
        Pattern pattern = Pattern.compile(NAME + "(\\d+)" + TABLE + "(\\.gz)?");

        for (File file : files) {
            Matcher matcher = pattern.matcher(file.getName());
            if (matcher.find()) {
                int counter = Integer.parseInt(matcher.group(1));
                if (counter > maxCounter) {
                    maxCounter = counter;
                }
            }
        }
        return maxCounter + 1;
    }

    private String genFilePath(int fileCounter) {
        return this.dataDir + File.separator + NAME + fileCounter + TABLE;
    }

    private String getCurrentFilePath() {
        return this.dataDir + File.separator + NAME + TABLE;
    }

    private String getGzFilePath(int fileCounter) {
        return this.dataDir + File.separator + NAME + fileCounter + TABLE + ".gz";
    }

    public void reloadIndex() {
        indexLock.writeLock().lock();
        try {
            for (int i = 0; i < fileCounter; i++) {
                String filePath = this.genFilePath(i);


                //除了data.table已经没有新的table了，只需检查gz文件即可
                if (filePath.endsWith(".gz")) {
//                    long fileLength = new File(filePath).length();
//                    // TODO: 2024/7/2 解压有问题，导致新创建文件没有内容
//                    byte[] decompressedData = decompressGZFile(filePath, 0, (int) fileLength);
//                    ByteArrayInputStream bais = new ByteArrayInputStream(decompressedData);
//                    loadCommandsFromStream(bais);
                } else {
                    RandomAccessFile currentFile = new RandomAccessFile(filePath, RW_MODE);
                    loadCommandsFromStream(new FileInputStream(currentFile.getFD()));
                }
            }
        } catch (Exception e) {
            LoggerUtil.error(LOGGER, e, logFormat, "reloadIndex");
        } finally {
            indexLock.writeLock().unlock();
        }
        LoggerUtil.debug(LOGGER, logFormat, "reload index: " + index.toString());
    }

    private void loadCommandsFromStream(InputStream inputStream) throws IOException {
        try (BufferedInputStream bis = new BufferedInputStream(inputStream)) {
            byte[] lengthBytes = new byte[4];
            while (bis.read(lengthBytes) != -1) {
                int cmdLen = ByteBuffer.wrap(lengthBytes).getInt();
                byte[] bytes = new byte[cmdLen];
                bis.read(bytes);
                JSONObject value = JSON.parseObject(new String(bytes, StandardCharsets.UTF_8));
                Command command = CommandUtil.jsonToCommand(value);
                if (command != null) {
                    CommandPos cmdPos = new CommandPos(-1, cmdLen);
                    index.put(command.getKey(), cmdPos);
                }
            }
        }
    }

    private synchronized void checkAndRotateIfNeeded() throws IOException {
        if (this.writerReader == null) {
            this.writerReader = new RandomAccessFile(this.getCurrentFilePath(), RW_MODE);
        }
        System.out.println(this.writerReader.length());
        if (this.writerReader.length() >= FILE_SIZE_THRESHOLD) {
            rotateFile();
        }
    }

    private void rotateFile() throws IOException {
        indexLock.writeLock().lock();
        try {
            // 读取当前文件内容到 memTable
            loadMemTableFromFile(getCurrentFilePath());

            if (this.writerReader != null) {
                this.writerReader.close();
            }

            // 生成旋转文件的路径
            String rotatedFilePath = genFilePath(fileCounter);

//            // 将当前文件移动到旋转文件路径
//            Files.move(Paths.get(getCurrentFilePath()), Paths.get(rotatedFilePath));

            // 打开新的当前文件进行写入
            this.writerReader = new RandomAccessFile(rotatedFilePath, RW_MODE);

            // 将 memTable 的内容写入新文件
            for (Map.Entry<String, Command> entry : memTable.entrySet()) {
                Command command = entry.getValue();
                byte[] commandBytes = JSONObject.toJSONBytes(command);
                RandomAccessFileUtil.writeInt(rotatedFilePath, commandBytes.length);
                long pos = RandomAccessFileUtil.write(rotatedFilePath, commandBytes);
                CommandPos cmdPos = new CommandPos(pos, commandBytes.length);
                this.index.put(entry.getKey(), cmdPos);
            }

            // 清空 memTable
            memTable.clear();

            // 增加文件计数器
            fileCounter++;

            // 异步压缩旋转文件
            CompressionUtils.compressFileAsync(rotatedFilePath, true);
        } finally {
            indexLock.writeLock().unlock();
        }
    }



    private synchronized void flushMemTableToDisk() throws IOException {
        if (memTable.isEmpty()) return;

        for (HashMap.Entry<String, Command> entry : memTable.entrySet()) {
            Command command = entry.getValue();
            byte[] commandBytes = JSONObject.toJSONBytes(command);
            RandomAccessFileUtil.writeInt(this.getCurrentFilePath(), commandBytes.length);
            long pos = RandomAccessFileUtil.write(this.getCurrentFilePath(), commandBytes);
            CommandPos cmdPos = new CommandPos(pos, commandBytes.length);
            this.index.put(entry.getKey(), cmdPos);
        }

        memTable.clear();
        checkAndRotateIfNeeded();
    }

    @Override
    public void set(String key, String value) {
        SetCommand command = new SetCommand(key, value);
        indexLock.writeLock().lock();
        try {
            memTable.put(key, command);
            if (memTable.size() >= storeThreshold) {
                flushMemTableToDisk();
            }
        } catch (IOException e) {
            throw new RuntimeException("Failed to set value", e);
        } finally {
            indexLock.writeLock().unlock();
        }
    }

    @Override
    public String get(String key) {
        indexLock.readLock().lock();
        try {
            // Step 1: Check in memTable
            Command cachedCommand = memTable.get(key);
            if (cachedCommand != null) {
                if (cachedCommand instanceof SetCommand) {
                    return ((SetCommand) cachedCommand).getValue();
                } else if (cachedCommand instanceof RmCommand) {
                    return null;
                }
            }

            // Step 2: Check in current file using index
            CommandPos cmdPos = index.get(key);
            if (cmdPos != null) {
                try {
                    byte[] commandBytes = RandomAccessFileUtil.readByIndex(getCurrentFilePath(), cmdPos.getPos(), cmdPos.getLen());
                    JSONObject value = JSONObject.parseObject(new String(commandBytes));
                    Command cmd = CommandUtil.jsonToCommand(value);
                    if (cmd instanceof SetCommand) {
                        return ((SetCommand) cmd).getValue();
                    } else if (cmd instanceof RmCommand) {
                        return null;
                    }
                } catch (Exception e) {
                    System.err.println("Error reading from current file: " + e.getMessage());
                }
            }


        } catch (Exception e) {
            throw new RuntimeException("Failed to get value", e);
        } finally {
            indexLock.readLock().unlock();
        }
        return null;
    }


    @Override
    public void rm(String key) {
        RmCommand command = new RmCommand(key);
        indexLock.writeLock().lock();
        try {
            memTable.put(key, command);
            if (memTable.size() >= storeThreshold) {
                flushMemTableToDisk();
            }
        } catch (IOException e) {
            throw new RuntimeException("Failed to remove value", e);
        } finally {
            indexLock.writeLock().unlock();
        }
    }

    //读取table到内存表
    private void loadMemTableFromFile(String filePath) throws IOException {
        try (RandomAccessFile file = new RandomAccessFile(filePath, "r")) {
            byte[] lengthBytes = new byte[4];
            while (file.read(lengthBytes) != -1) {
                int cmdLen = ByteBuffer.wrap(lengthBytes).getInt();
                byte[] bytes = new byte[cmdLen];
                file.read(bytes);
                JSONObject value = JSON.parseObject(new String(bytes, StandardCharsets.UTF_8));
                Command command = CommandUtil.jsonToCommand(value);
                if (command != null) {
                    memTable.put(command.getKey(), command);
                }
            }
        }
    }


    @Override
    public void close() throws IOException {
        indexLock.writeLock().lock();
        try {
            if (writerReader != null) {
                writerReader.close();
            }
            //不管有没有超过阈值，在关闭前都要将内存表的内容写入磁盘
            flushMemTableToDisk();
            // 清空内存表
            memTable.clear();
        } finally {
            indexLock.writeLock().unlock();
        }
    }
}
